筆記性質
菜鳥初學,為了避免誤人子弟,請大神留步勘誤 QQ
建議搭配服用: JS Scope / Hoisting
function a() {
var c = 1
}
a()
console.log('123')
概念上我們可以把一個 執行環境 想像成一個物件內包含:
executionContextObject = {
scopeChain: {},
variableObject: {},
this: {}
}
建立階段,初始化這個環境
除 arguments 外都只是先定義變數 & 函式指標,並沒有賦值
進行 Hoisting
初始化 scope chain
判斷決定 this 的值 (因此 this 在函式呼叫時才被決定)
建 variableObject
建 arguments object 並給予值
將 function 的宣告 加入 variableObject
將 變數 的宣告 加入 variableObject
給與値、設定 Function 的參考、逐行解譯執行程式碼
function foo(i) {
var a = 'hello';
// 建立階段 函式表達 不給與 值/指標
var b = function B() { };
// 建立階段 函式宣告 給與指標
function c() { }
}
foo(22);
// 建立階段 / 執行階段
fooExecutionContext = {
scopeChain: { ... },
variableObject: {
// arguments object
// 0: 表第一個傳入的參數 1: 表2......
//! 參數的值 會在建立階段就給與。
arguments: { 0: 22, length: 1 },
i: 22,
a: undefined, // 未給與値
b: undefined, // 函式運算 不給與値
c: pointer to function c(), // 函式宣告 給與指標
// 執行階段才給與値 進行覆寫建立階段建立的變數 / 其他不變
a: 'hello',
b: pointer to function B()
},
this: { ... }
}
定義函式又分為三種
var a = function () {}
function b() { }
;(function () {
// 建立階段 a 為 函式運算式 未给值 undefined
// 建立階段 b 為 函式宣告 給予指標 pointer to function b()
console.log(typeof a) // undefined
console.log(typeof b) // function
// 執行階段 給與值 pointer to function a()
var a = function () {}
console.log(typeof a) // function
// 執行階段 給與值 進行覆寫
var b = 123
console.log(typeof b) // number
function b() { }
// 執行階段 給值 a : pointer to function a()
// 執行階段 給與值 b : 123
}())
// Hoisting 函數宣告 優先於 變數宣告 (不論 Code相對位置)
function a() {}
var a // 並沒有重新給值,因此 執行階段此行不會執行
console.log(typeof a) // function
// 於執行階段給予值
var b = 1
function b() {}
console.log(typeof b) // number
請解釋
(function () {
console.log("foo: " + typeof foo)
var foo = 'hey'
console.log("foo: " + typeof foo)
function foo() { return 'hello' }
console.log("bar: " + typeof bar)
var bar = function() { return 'world' }
console.log("bar: " + typeof bar)
}())
Ans: 1.function / 2.string / 3.undefined / 4.function
// 建立階段: 1.函式宣告 給座標、3.函式運算 不給值
// 執行階段: 2. var foo = 'hey' foo 給值為 string
// 執行階段: 4. var bar = func~ bar 給值為 function
Why 可在宣告前存取 foo
在建立階段我們就已經將變數建立了 [Hoisting]
Foo 被宣告 2 次,為什麼 foo 是 function 而不是 undefined 或 string ?
第一個 log()
第二個 log()
因為執行階段會給於值,因此 foo 被改寫成 str 'hey'
var c = 1
function c() {
console.log(typeof c)
}
console.log(c)
c()
Ans: c is not a function
console.log(c) // 1
// 建立階段 Hoisting c = function pointer
// 執行階段給予值 先讀 c = 1 後執行 c() 此時 c 給值為 1
// 因此 c is not a function
大大的文章很棒,小弟學到很多新知識,不過有個地方覺得困惑,分享一下,
文中有提到 建立階段: Hoisting 函數宣告 優先於 變數宣告
,
可是在建立階段,執行順序是:
如果按照後壓前的邏輯,建立階段 變數宣告
應該是會蓋過 函數宣告
的。
小弟後來查了一下,發現在建立階段處理變數的過程中,
會將變數加入 variableObject,並初始化為 undefined,
不過如果變數名稱已經存在,會略過不處理,
如果是這樣就可以解釋為什麼,Hoisting 函數宣告 優先於 變數宣告
。
不知道小弟的猜測對不對,還是有其他原因和這個無關呢,哈哈哈。
我來串一下門子XD,大大提出的下面這句話是對的!不過如果變數名稱已經存在,會略過不處理,
所以才會有函數宣告 優先於 變數宣告
現象!
一直到執行階段,相同的變數名稱再一次被賦值為止
我是通過測試下列的 Code 得出的 Hoisting函數宣告優先於變數宣告的結論。
下列 a,b情況 都沒有進行賦値,所以都不會在執行階段進行覆寫變數。
因此所有宣告 a,b 變數的動作應該都是在建立階段執行的。
不論下面 a 情況或 b情況,變數、函式程式碼的位置順序不同,回傳結果都是 Function。
而得出 Hoisting函數宣告優先於變數宣告這個結論。
var a
function a() {}
console.log(typeof a) // function
function b() {}
var b
console.log(typeof b) // function
以上是個人見解沒有文件 Support,
可以的話提供一下 "如果變數名稱已經存在,會略過不處理" 的出處。
我蠻想再去深入了解的 ~
我只是個菜鳥 大家互相交流交流 !
補個資料
詳細解說 Variable object,最上方有翻譯版本可以選擇。
我也來研究一下..
不好意思,文章中的程式碼借我用一下XD
(function () {
console.log("foo: " + typeof foo) //(1)
var foo = 'hey'
console.log("foo: " + typeof foo) //(2)
function foo() { return 'hello' }
console.log("foo: " + typeof foo) //(3)
}())
以下環境建立的時候,會先看有沒有帶入參數,再來偵測function
的宣告找到function foo(){}
,所以在(1)的console.log會印出function
類型,然後會再去尋找有沒有宣告變數,這時後發現有var foo = 'hey'
,不過會因為foo
這個名稱已經被指標建立function
所以他就會略過這個變數,因此foo
不會在(1)的時候被這行蓋過變成印出undefined
,環境就在這時候建立完畢了!
進入執行階段foo
被賦值為'hey'所以才在(2)的時候會印出string!
可以再加碼看個(3),因為再執行階段的時候foo已經被賦值成'hey',所以雖然再下方還是有function foo(){}
宣告,但這時候(3)依然會印出string,因為執行階段在建立階段之後。
所以RocMark大大文章說的沒有錯,再建立階段函數宣告優先於變數宣告
,而fysh711426大說的也對,因為指標再function身上,所以會忽略後面再宣告的var foo
,就是變數名稱已經存在,會略過不處理
。
啊因為我也還再學習,所以以上如果有問題也可以提出討論!我也不一定是對的,因為我也很菜XD
哈哈哈,也不算專家,只是很喜歡JS所以之前買一堆書回來看
不過樓主真的超級猛,居然自己測試出那麼多東西!
感謝兩位的支援 XD 又多學了一點東西
執行環境是指Execution Context?如果是的話,除了Function,記得還有Global跟Eval這兩種。Global你好像有提到一些就是了。
文章寫的很詳細,加油阿!!!
執行環境是指Execution Context 沒錯
Eval 我本身有用ESLint在檢查代碼,看了一下ESLint的 Rule,是說不建議使用,因為效能不佳,目前我都只在自學,不是很清楚在實務上會不會用到,所以就先略過了。
至於 Global 環境,運行原理應該與 Function 相同,如有誤歡迎大大幫補充 XD
補充個Global的資料以便日後複習